import React, { FC, useState, useEffect, ChangeEvent, ReactElement, KeyboardEvent, useRef } from 'react';
import classNames from 'classnames';
import { Input, InputProps } from '../Input/input';
import { Icon } from '../Icon/icon';
import useDebounce from '../../hooks/useDebounce';
import useClickOutside from '../../hooks/useClickOutside';
import Transition from '../Transition/transition';

interface DataSourceObject {
  value: string;
}

export type DataSourceType<T = {}> = T & DataSourceObject;

export interface AutoCompleteProps extends Omit<InputProps, 'onSelect'> {
  fetchSuggestions: (str: string) => DataSourceType[] | Promise<DataSourceType[]>;
  onSelect?: (item: DataSourceType) => void;
  renderOption?: (item: DataSourceType) => ReactElement;
}

export const AutoComplete: FC<AutoCompleteProps> = (props) => {
  const {
    fetchSuggestions,
    onSelect,
    value,
    renderOption,
    ...restProps
  } = props;

  const [inputValue, setInputValue] = useState(value as string);
  const [suggestions, setSuggestions] = useState<DataSourceType[]>([]);
  const [loading, setLoading] = useState(false);
  const [showDropdown, setShowDropdown] = useState(false)
  // 选中高亮
  const [highlightIndex, setHighlightIndex] = useState(-1);
  const triggerSearch = useRef(false);
  const componentRef = useRef<HTMLDivElement>(null);
  useClickOutside(componentRef, () => { setSuggestions([]) });
  // 自定义 hook 防抖, 默认 300
  const debounceValue = useDebounce(inputValue, 300);
  useEffect(() => {
    if (debounceValue && triggerSearch.current) {
      setSuggestions([])
      const result = fetchSuggestions(debounceValue);
      if (result instanceof Promise) {
        setLoading(true);
        result.then(data => {
          setLoading(false);
          setSuggestions(data)
          if (data.length > 0) {
            setShowDropdown(true)
          }
        })
      } else {
        setSuggestions(result);
        if (result.length > 0) {
          setShowDropdown(true)
        }
      }
    } else {
      setShowDropdown(false);
    }
    setHighlightIndex(-1);
  }, [debounceValue, fetchSuggestions]);

  const highlight = (index: number) => {
    if (index < 0) index = suggestions.length - 1;
    else if (index >= suggestions.length) {
      index = 0
    }
    setHighlightIndex(index);
  }
  
  // 键盘事件
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    switch (e.keyCode) {
      case 13: // 回车
        if (suggestions[highlightIndex]) {
          handleSelect(suggestions[highlightIndex])
        }
        break;
      case 38: // 向上箭头
        highlight(highlightIndex - 1)
        break;
      case 40: // 向下箭头
        highlight(highlightIndex + 1)
        break;
      case 27: // Esc
        setShowDropdown(false)
        break;
      default:
        break;
    }
  }

  // 值改变
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value.trim();
    setInputValue(value);
    triggerSearch.current = true;
  }

  const handleSelect = (item: DataSourceType) => {
    setInputValue(item.value);
    setShowDropdown(false);
    if (onSelect) {
      onSelect(item);
    }
    triggerSearch.current = false;
  }

  // 自定渲染
  const renderTemplate = (item: DataSourceType) => {
    return renderOption ? renderOption(item) : item.value;
  }

  // 下拉列表
  const generateDropdown = () => {
    return (
      <Transition
        in={showDropdown || loading}
        animation="zoom-in-top"
        timeout={300}
        onExited={() => {setSuggestions([])}}
      >
        <ul className="origin-suggestion-list">
          { loading &&
            <div className="origin-suggestion-loading-icon">
              <Icon icon="spinner" spin/>
            </div>
          }
          {suggestions.map((item, index) => {
            const classes = classNames('origin-suggestion-item', {
              'origin-suggestion-item-is-active': index === highlightIndex
            })
            return (
              <li key={index} className={classes} onClick={() => handleSelect(item)}>
                {renderTemplate(item)}
              </li>
            )
          })}
        </ul>
      </Transition>
    )
  }

  return (<div className="origin-auto-complete" ref={ componentRef }>
    <Input
      {...restProps}
      value={inputValue}
      onChange={handleChange}
      onKeyDown={handleKeyDown}
    />
    {generateDropdown()}
  </div>)
}

export default AutoComplete;